﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Runtime.Serialization;
using System.ServiceModel.Web;
using LyncServerExtension.Monitoring;
using System.ServiceModel;

namespace LyncServerExtension.WebService
{
    /// <summary>
    /// Class which implements the HTTP web service.
    /// </summary>
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class LyncWebService : ILyncWebService, IDisposable
    {
        private LyncServerProxy serverProxy;
        private LyncResultBuilderPluginProvider resultBuilderProvider;
        private LyncDelegationPolicy delegationPolicy;
        private IEventLog eventLog;

        /// <summary>
        /// Initializes a new instance of the <see cref="LyncWebService"/> class.
        /// </summary>
        /// <param name="domainName">Name of the domain.</param>
        /// <param name="serverName">Name of the server.</param>
        /// <param name="sefaUtilPath">The SEFAUtil tool path.</param>
        /// <param name="pluginDirectoryPath">The plugin directory path.</param>
        /// <param name="delegationPolicyFilePath">The delegation policy file path.</param>
        /// <param name="logDirectoryPath">The log directory path.</param>
        public LyncWebService(string domainName, string serverName, string sefaUtilPath, string pluginDirectoryPath, string delegationPolicyFilePath, string logDirectoryPath)
        {
            if (pluginDirectoryPath == null)
                throw new ArgumentNullException("pluginDirectoryPath");

            if (delegationPolicyFilePath == null)
                throw new ArgumentNullException("delegationPolicyFilePath");

            eventLog = new FileEventLog(logDirectoryPath);
            serverProxy = new LyncServerProxy(domainName, serverName, sefaUtilPath, eventLog);
            resultBuilderProvider = new LyncResultBuilderPluginProvider(pluginDirectoryPath);

            eventLog.Log(EventType.Information, "Loading delegation policy.");
            try
            {
                delegationPolicy = LyncDelegationPolicy.Load(delegationPolicyFilePath);
            }
            catch (Exception ex)
            {
                eventLog.Log(EventType.Error, "Unable to load delegation policy: " + ex.Message);
                Environment.Exit(-1);
            }

            eventLog.Log(EventType.Information, "Loading plugins.");
            try
            {
                resultBuilderProvider.LoadPlugins();
            }
            catch (Exception ex)
            {
                eventLog.Log(EventType.Error, "Unable to load plugins: " + ex.Message);
                Environment.Exit(-1);
            }
            eventLog.Log(EventType.Information, "Service started.");
        }

        public void Dispose()
        {
            eventLog.Log(EventType.Information, "Unloading plugins.");
            resultBuilderProvider.UnloadPlugins();
            eventLog.Log(EventType.Information, "Service stopped.");
        }

        /// <summary>
        /// Lists the authorized owners.
        /// </summary>
        /// <param name="id">The plugin id.</param>
        /// <param name="culture">The culture.</param>
        /// <returns></returns>
        public Stream ListAuthorizedOwners(string id, string culture)
        {
            return ProcessOperation("ListAuthorizedOwners", id, culture, (accountName, resultBuilder) =>
                resultBuilder.SetAuthorizedOwners(delegationPolicy.FilterAuthorizedOwners(accountName, serverProxy.UserMap)));
        }

        public Stream ListAuthorizedDelegates(string id, string culture, string ownerSip)
        {
            ownerSip = ownerSip.ToLower();

            return ProcessOperation("ListAuthorizedDelegates", id, culture, (accountName, resultBuilder) =>
                {
                    if (string.IsNullOrEmpty(ownerSip))
                        return MissingParameterError(resultBuilder, "ownerSip");

                    return resultBuilder.SetAuthorizedDelegates(ownerSip,
                        delegationPolicy.FilterAuthorizedDelegates(accountName, ownerSip, serverProxy.UserMap));
                }
            );
        }

        /// <summary>
        /// Lists the current delegates of the given owner.
        /// </summary>
        /// <param name="id">The plugin id.</param>
        /// <param name="culture">The culture.</param>
        /// <param name="ownerSip">The owner sip.</param>
        /// <returns></returns>
        public Stream ListDelegates(string id, string culture, string ownerSip)
        {
            ownerSip = ownerSip.ToLower();

            return ProcessOperation("ListDelegates", id, culture, (accountName, resultBuilder) =>
                {
                    if (string.IsNullOrEmpty(ownerSip))
                        return MissingParameterError(resultBuilder, "ownerSip");

                    return resultBuilder.SetDelegates(ownerSip, serverProxy.GetDelegates(ownerSip));
                }
            );
        }

        /// <summary>
        /// Gets the user photo.
        /// </summary>
        /// <param name="sip">The user sip.</param>
        /// <returns></returns>
        public Stream GetUserPhoto(string sip)
        {
            LyncUser user;

            if (ServiceSecurityContext.Current.WindowsIdentity == null)
                return null;

            string accountName = ServiceSecurityContext.Current.WindowsIdentity.Name.ToLower();

            if (!delegationPolicy.IsAuthorized(accountName) || (sip == null) ||
                !serverProxy.UserMap.TryGetValue(sip, out user) || (user.Photo == null))
                return null;

            WebOperationContext.Current.OutgoingResponse.ContentType = "image/jpeg";
            return new MemoryStream(user.Photo);
        }

        /// <summary>
        /// Sets the call forwarding delegation.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <param name="culture">The culture.</param>
        /// <param name="enabled">if set to <c>true</c> [enabled].</param>
        /// <param name="owner">The owner.</param>
        /// <param name="target">The target.</param>
        /// <param name="state">The state.</param>
        /// <returns></returns>
        public Stream SetCallForwardingDelegation(string id, string culture, bool enabled, string owner, string target, string state)
        {
            return ProcessOperation("SetCallForwardingDelegation", id, culture,
                (accountName, resultBuilder) =>
                {
                    if (string.IsNullOrEmpty(owner))
                        return MissingParameterError(resultBuilder, "owner");

                    if (string.IsNullOrEmpty(target))
                        return MissingParameterError(resultBuilder, "target");

                    LyncCallForwardingParameter param = new LyncCallForwardingParameter
                    {
                        IsEnabled = enabled,
                        OwnerSip = owner.ToLower(),
                        DelegateSip = target.ToLower()
                    };

                    if (!(delegationPolicy.IsAuthorizedDelegation(accountName, param.OwnerSip, param.DelegateSip)))
                        throw new LyncServerExtensionException("Operation denied.");

                    serverProxy.SetCallForwardingDelegation(param);
                    return resultBuilder.SetCallForwardingDelegation(param, state);
                }
            );
        }

        private ILyncResult MissingParameterError(ILyncResultBuilder resultBuilder, string parameter)
        {
            return resultBuilder.Error("Missing required parameter: " + parameter);
        }

        /// <summary>
        /// Processes the given operation if authorized and captures, logs exceptions if any.
        /// </summary>
        /// <param name="operationName">Name of the operation.</param>
        /// <param name="id">The id.</param>
        /// <param name="culture">The culture.</param>
        /// <param name="operation">The operation.</param>
        /// <returns></returns>
        private Stream ProcessOperation(string operationName, string id, string culture, Func<string, ILyncResultBuilder, ILyncResult> operation)
        {
            ILyncResult result;
            ILyncResultBuilder resultBuilder = GetResultBuilder(id, culture);
            string accountName = null;

            if (resultBuilder == null)
                return null;

            try
            {
                if (ServiceSecurityContext.Current.WindowsIdentity == null)
                    throw new LyncServerExtensionException("Windows identity not provided.");

                accountName = ServiceSecurityContext.Current.WindowsIdentity.Name.ToLower();
                eventLog.Log(EventType.Information, "Processing operation '{0}' with account name '{1}'.", operationName, accountName);

                if (!delegationPolicy.IsAuthorized(accountName))
                {
                    throw new LyncServerExtensionException("Operation denied.");
                }
                result = operation(accountName, resultBuilder);
            }
            catch (Exception ex)
            {
                eventLog.Log(EventType.Warning, "Operation '{0}' failed with account name '{1}': {2}.", operationName, accountName, ex.Message);
                return ResultToStream(resultBuilder.Error(ex.Message));
            }
            return (result == null) ? null : ResultToStream(result);
        }

        private ILyncResultBuilder GetResultBuilder(string id, string culture)
        {
            if (id == null)
                return null;

            if (culture == null)
                culture = string.Empty;

            return resultBuilderProvider.GetResultBuilder(id, culture);
        }

        private Stream ResultToStream(ILyncResult result)
        {
            Stream stream = new MemoryStream();
            WebOperationContext.Current.OutgoingResponse.ContentType = result.MimeType;
            result.Write(stream);
            stream.Position = 0;
            return stream;
        }
    }
}